package com.hm.gateway.filter;

import java.math.BigInteger;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.hm.common.Constant;
import com.hm.common.api.ApiResult;
import com.hm.common.api.ApiStatus;
import com.hm.common.utils.StringUtils;
import com.hm.gateway.JWTProperties;
import com.hm.gateway.service.AuthService;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 
 * @author hwg
 *
 *         2020年12月18日
 */
@Component
public class AuthFilter implements GlobalFilter, Ordered {
	Logger logger = LoggerFactory.getLogger(AuthFilter.class);

	@Autowired
	private JWTProperties jwtProperties;

	@Autowired
	private AuthService authService;

	private AntPathMatcher antPathMatcher = new AntPathMatcher();;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		String reqId = exchange.getRequest().getId();
		String path = exchange.getRequest().getURI().getPath();

		ServerHttpRequest request = exchange.getRequest().mutate().header(Constant.REQUEST_ID, reqId).build();

		for (String ignoreUrl : jwtProperties.getIgnoreUrls()) {
			if (antPathMatcher.match(ignoreUrl, path)) {
				return chain.filter(exchange.mutate().request(request).build());
			}
		}

		String token = exchange.getRequest().getHeaders().getFirst(Constant.token);

		//判断客户端是接受html还是json，非html的都返回json，如果是接受html的，验证token失败后重定向到登录界面
		boolean isAcceptHtml = isAcceptHtml(exchange);

		if (StringUtils.isEmpty(token)) {
			return isAcceptHtml ? doRedirect(exchange) : doAuthError(exchange, reqId, path);
		}

		//验证token
		Map<String, Object> tokenMap = authService.findToken(token);
		//验证token，此次验证是验证token是否失效，最后一次访问时间到目前超过30分钟，数据库自动删除，数据库中不存在则为失效
		if (tokenMap == null || tokenMap.size() == 0) {
			return isAcceptHtml ? doRedirect(exchange) : doAuthError(exchange, reqId, path);
		}

		//验证通过后，刷新token访问时间
		authService.refreshToken(token);

		String userId = ((BigInteger) tokenMap.get("userId")).toString();
		request = request.mutate().header(Constant.USER_ID, userId).build();
		return chain.filter(exchange.mutate().request(request).build());
	}

	private boolean isAcceptHtml(ServerWebExchange exchange) {
		String accept = exchange.getRequest().getHeaders().getFirst("Accept");
		return StringUtils.isNotEmpty(accept) && accept.contains("text/html");
	}

	private Mono<Void> doRedirect(ServerWebExchange exchange) {
		logger.info("重定向到URL: {}", jwtProperties.getRedirectUrl());
		exchange.getResponse().getHeaders().set(HttpHeaders.LOCATION, jwtProperties.getRedirectUrl());
		exchange.getResponse().setStatusCode(HttpStatus.SEE_OTHER);
		exchange.getResponse().getHeaders().add(Constant.CONTENT_TYPE, Constant.CONTENT_TYPE_HTML);
		return exchange.getResponse().setComplete();
	}

	/**
	 * 认证错误输出
	 * 
	 * @param response
	 * @param reqId
	 * @param message
	 * @param path
	 * @return
	 */
	private Mono<Void> doAuthError(ServerWebExchange exchange, String reqId, String path) {
		exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
		exchange.getResponse().getHeaders().add(Constant.CONTENT_TYPE, Constant.CONTENT_TYPE_JSON);
		DataBuffer buffer = null;
		try {
			buffer = exchange.getResponse().bufferFactory().wrap(ApiResult.of(reqId, ApiStatus.STATUS_401, path).toJson().getBytes());
		} catch (JsonProcessingException e) {
			logger.error(e.getMessage(), e);
		}
		return exchange.getResponse().writeWith(Flux.just(buffer));
	}

	@Override
	public int getOrder() {
		return -999;
	}

}
